package com.ruoyi.framework.web.exception;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.exception.base.BaseException;
import com.ruoyi.common.exception.user.CaptchaExpireException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.boot.autoconfigure.web.servlet.MultipartProperties;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.UncategorizedDataAccessException;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.jdbc.BadSqlGrammarException;
import org.springframework.jdbc.UncategorizedSQLException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.transaction.CannotCreateTransactionException;
import org.springframework.transaction.TransactionException;
import org.springframework.util.unit.DataSize;
import org.springframework.validation.BindException;
import org.springframework.validation.Errors;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentConversionNotSupportedException;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.sql.SQLException;
import java.sql.SQLSyntaxErrorException;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 全局异常处理器
 *
 * @author ruoyi
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 权限校验异常
     */
    @ExceptionHandler(AccessDeniedException.class)
    public AjaxResult handleAccessDeniedException(AccessDeniedException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',权限校验失败'{}'", requestURI, e.getMessage());
        return AjaxResult.error(HttpStatus.FORBIDDEN, "没有权限，请联系管理员授权");
    }

    /**
     * 请求方式不支持
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public AjaxResult handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e,
                                                          HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod());
        return AjaxResult.error(StrUtil.format("请求方法不支持'{}'请求", e.getMethod()));
    }

    /**
     * 业务异常
     */
    @ExceptionHandler(ServiceException.class)
    public AjaxResult handleServiceException(ServiceException e) {
        log.error(e.getMessage(), e);
        Integer code = e.getCode();
        return StringUtils.isNotNull(code) ?
                AjaxResult.error(code, e.getMessage()) : AjaxResult.error(e.getMessage());
    }

    /**
     * 拦截未知的运行时异常
     */
    @ExceptionHandler(RuntimeException.class)
    public AjaxResult handleRuntimeException(RuntimeException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',发生未知异常.", requestURI, e);
        return AjaxResult.error(e.getMessage());
    }

    /**
     * 系统异常
     */
    @ExceptionHandler(Exception.class)
    public AjaxResult handleException(Exception e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',发生系统异常.", requestURI, e);
        return AjaxResult.error(e.getMessage());
    }

    /**
     * 自定义验证异常
     */
    @ExceptionHandler(BindException.class)
    public AjaxResult handleBindException(BindException e) {
        log.error(e.getMessage(), e);
        String message = e.getAllErrors().get(0).getDefaultMessage();
        return AjaxResult.error(message);
    }

    /**
     * 处理 sql  异常
     *
     * @param e SQL...Exception
     * @return AjaxResult
     */
    @ExceptionHandler(value = {
            SQLException.class,
            UncategorizedSQLException.class,
            UncategorizedDataAccessException.class,
            CannotCreateTransactionException.class,
            TransactionException.class,
            SQLSyntaxErrorException.class,
            BadSqlGrammarException.class,
            DataIntegrityViolationException.class})
    @ResponseStatus(org.springframework.http.HttpStatus.OK)
    @ResponseBody
    public AjaxResult exceptionHandlerSqlException(Exception e) {
        log.error("sql异常:", e);
        return AjaxResult.error(org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR.value(), "数据库访问出现异常,请联系管理员!");
    }

    /**
     * 自定义验证异常
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        log.error("方法入参异常", e);
        String errorMessage = Optional.of(e.getBindingResult())
                .map(Errors::getAllErrors)
                .filter(CollectionUtil::isNotEmpty)
                .flatMap(errors -> errors.stream()
                        .findAny())
                .map(DefaultMessageSourceResolvable::getDefaultMessage)
                .orElse(null);
        if (StringUtils.isBlank(errorMessage)) {
            errorMessage = e.getMessage();
        }
        return AjaxResult.error(errorMessage);
    }

    /**
     * 方法入参校验异常
     *
     * @param e ConstraintViolationException报告违反约束的结果
     * @return AjaxResult
     */
    @ExceptionHandler(value = ConstraintViolationException.class)
    @ResponseBody
    @ResponseStatus(org.springframework.http.HttpStatus.OK)
    public AjaxResult exceptionHandler(ConstraintViolationException e) {
        Set<ConstraintViolation<?>> constraintViolations = e.getConstraintViolations();
        String message = constraintViolations.stream().filter(Objects::nonNull)
                .map(ConstraintViolation::getMessage)
                .collect(Collectors.joining("", "[", "]"));
        return AjaxResult.error(org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR.value(), message);
    }


    /**
     * 参数类型转换异常
     *
     * @param e MethodArgumentConversionNotSupportedException
     * @return AjaxResult
     */
    @ExceptionHandler(value = MethodArgumentConversionNotSupportedException.class)
    @ResponseBody
    @ResponseStatus(org.springframework.http.HttpStatus.OK)
    public AjaxResult exceptionHandler(MethodArgumentConversionNotSupportedException e) {
        log.error("error:", e);
        String message = StrUtil.format("参数:[{}]类型转换错误,值无法转换为相对的类型!", e.getName());
        return AjaxResult.error(org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR.value(), message);
    }


    /**
     * 处理MVC 报错
     *
     * @param e HttpMessageNotReadableException
     * @return AjaxResult
     */
    @ExceptionHandler(value = HttpMessageNotReadableException.class)
    @ResponseStatus(org.springframework.http.HttpStatus.OK)
    @ResponseBody
    public AjaxResult exceptionHandler(HttpMessageNotReadableException e) {
        log.error("HttpMessageNotReadableException异常:", e);
        return AjaxResult.error(org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR.value(), "请求体消息不可读");
    }


    /**
     * 请求路径不存在异常
     *
     * @param e NoHandlerFoundException
     * @return Ajax
     */
    @ExceptionHandler(value = NoHandlerFoundException.class)
    @ResponseStatus(org.springframework.http.HttpStatus.OK)
    @ResponseBody
    public AjaxResult exceptionHandler(NoHandlerFoundException e) {
        log.error("系统出现异常:", e);
        return AjaxResult.error(org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR.value(),
                String.format("不存在该请求路径,请求路径:'%s',请求方法:'%s'", e.getRequestURL(), e.getHttpMethod()));
    }


    /**
     * 控制器入参必填校验异常处理
     *
     * @param e e
     * @return obj
     */
    @ExceptionHandler(value = MissingServletRequestParameterException.class)
    @ResponseStatus(value = org.springframework.http.HttpStatus.OK)
    @ResponseBody
    public AjaxResult exceptionHandler(MissingServletRequestParameterException e) {
        log.error("控制器入参必填参数校验异常:", e);
        return AjaxResult.error(org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR.value(),
                String.format("缺少必填参数:'%s',类型:'%s'", e.getParameterName(), e.getParameterType()));
    }

    /**
     * 基础异常 拦截处理
     *
     * @param e BaseException
     * @return AjaxResult
     */
    @ExceptionHandler(value = BaseException.class)
    @ResponseStatus(value = org.springframework.http.HttpStatus.OK)
    @ResponseBody
    public AjaxResult exceptionHandler(BaseException e) {
        log.error("", e);
        String code = e.getCode();
        return StringUtils.isEmpty(code) ?
                AjaxResult.error(Integer.parseInt(code), e.getMessage()) : AjaxResult.error(e.getMessage());
    }

    /**
     * 验证码失效异常处理
     *
     * @param e 异常
     * @return ajax
     */
    @ExceptionHandler(value = CaptchaExpireException.class)
    @ResponseStatus(value = org.springframework.http.HttpStatus.OK)
    @ResponseBody
    public AjaxResult exceptionHandler(CaptchaExpireException e) {
        log.error("", e);
        return AjaxResult.error(e.getCode(), e.getMessage());
    }


    /**
     * 文件上载 超出大小异常
     *
     * @param e MaxUploadSizeExceededException
     * @return Ajax
     */
    @ExceptionHandler(value = MaxUploadSizeExceededException.class)
    @ResponseStatus(value = org.springframework.http.HttpStatus.OK)
    @ResponseBody
    public AjaxResult exceptionHandler(MaxUploadSizeExceededException e) {
        log.error("", e);
        Double maxSizeMb = null;
        try {
            MultipartProperties bean = SpringUtils.getBean(MultipartProperties.class);
            DataSize maxFileSize = bean.getMaxFileSize();
            maxSizeMb = maxFileSize.toBytes() / 1024.0 / 1024.0;
        } catch (BeansException ignored) {
        }
        String errorMessage = "文件上传失败,文件大小超出了系统允许的最大值。";
        if (Objects.nonNull(maxSizeMb)) {
            errorMessage = errorMessage + "请确保文件大小不超过" + maxSizeMb + "MB。";
        }
        return AjaxResult.error(errorMessage);
    }

}
